package algorithms.disCBJ;

import general.TwoLongs;
import general.CPA.ArrayCPA;
import general.CPA.CPA;
import general.constraints.Constraints;

import java.util.Arrays;


import ordering.VariableOrdering;
import ordering.stat.DefaultOrdering;

import priorities.EqualPriority;
import priorities.PriorityManager;
import problem.Problem;

/**
 *	An Agent dispatcher, responsible for creating, connecting and running the agents
 */
public class DisCBJAgentMaker {
	byte numAgents;
	byte domain;
	DisCBJAgent[] disCBJAgents;

	/**
	 * Creates an instance of DisCBJAgentMaker
	 * @param numAgents number of agents to create
	 * @param domain domain for each agent
	 */
	public DisCBJAgentMaker(byte numAgents, byte domain){
		this.numAgents = numAgents;
		this.domain = domain;
		this.disCBJAgents = new DisCBJAgent[numAgents];
	}

	/**
	 * Creates the agents and connects them together
	 * First, creates each agent and starts them on listening mode (to listen for incoming connections from the other agents)
	 * (waits for the agents to be ready after each step)
	 * Second, tells all of the agents to connect to their friends, supplying the ports
	 * Third, for each agent that received all of the connections it needs and sent the rest - opens a selector
	 * Finally when all of the selectors are ready - this method returns
	 * @param constraints the constraints to assign to the agents
	 * @param ordering variable ordering heuristics to assign to the agents
	 * @param protoCpa a CPA prototype that the agents will use to create new CPAs
	 */
	public void createAgents(Constraints constraints, VariableOrdering ordering, CPA protoCpa, int delay){
		int[] ports = new int[numAgents];
		//creates the agents and start them on listening mode
		for (byte i=0; i<numAgents; i++){
			disCBJAgents[i] = new DisCBJAgent(i, domain,numAgents, constraints.getVariableConstraints(i), ordering, protoCpa, delay); 	//TODO
			ports[i] = disCBJAgents[i].startListening();
			disCBJAgents[i].getConnections();
		}

		//waits for all agents to start listening
		for (int i=0; i<numAgents; i++){
			while (!disCBJAgents[i].isReadyForConnections()) myWait(250);
		}

		//creates the connections between the agents
		for (int i=0; i<numAgents; i++){
			disCBJAgents[i].createConnections(Arrays.copyOfRange(ports, 0, i));
		}

		//creates a reactor for each of the ready agents
		for (int i=0; i<numAgents; i++){
			while (!disCBJAgents[i].isReadyForSelector()) myWait(250);
			disCBJAgents[i].printStatus();
			disCBJAgents[i].openSelector();
		}

		//waits for all of the agents to have their reactor ready
		for (int i=0; i<numAgents; i++){
			while (!disCBJAgents[i].isReadySelector()) myWait(250);
		}
//		System.out.println("All agents ready");
	}

	/**
	 * delays for a given amount of time
	 * @param milis milliseconds to wait
	 */
	private void myWait(long milis) {
//		System.out.println("Waiting " + milis + " miliseconds");
		try {
			String m = "";
			synchronized (m) {
				m.wait(milis);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Starts all of the agents and waits for all of them to finish
	 */
	private void startAllAgents(){
		//starts the agnets
		for (int i=0; i<disCBJAgents.length; i++){
			disCBJAgents[i].start();
		}

		try {
			//waits for the agents to return
			for (int i=0; i<disCBJAgents.length; i++){
				disCBJAgents[i].join();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
//		System.out.println("All threads are dead :)");

		for (int i=0; i<disCBJAgents.length; i++){
//			disCBJAgents[i].killTimer();
			disCBJAgents[i].close();
		}
	}

	/**
	 * Solves a problem and returns the total number of NCCCs and total number of messages sent
	 * (NCCCs is the first one in the TwoLongs object, number of messages is the second) 
	 * @param c constraints for the agents
	 * @param ordering variable ordering heuristics for the agents
	 * @param protoCpa prototype CPA for using with the agents
	 * @param delay - delay in milliseconds of the messages
	 * @return the total number of NCCCs and the total number of messages sent
	 */
	public TwoLongs solve(Constraints c, VariableOrdering ordering, CPA protoCpa, int delay, PriorityManager manager){
		PriorityManager.reset();
		PriorityManager.init(manager);
		createAgents(c, ordering, protoCpa, delay);
		startAllAgents();
		long NCCCs = 0;
		long messages = 0;
		for (int i=0; i<disCBJAgents.length; i++){
			NCCCs = NCCCs + disCBJAgents[i].getNCCs();
			messages = messages + disCBJAgents[i].getNumMessages();
		}
//		System.out.println(NCCCs + " " + messages);
		return new TwoLongs(NCCCs, messages);
	}
	
	//TODO
	public String getSolution(){
		return disCBJAgents[0].getSolution();
	}

	public static void main(String[] args) {
		byte n = 15;
		byte domain = n;
		DisCBJAgentMaker m = new DisCBJAgentMaker(n, domain);
		Constraints constraints = Problem.nQueensConstraints(n);
		//		m.solve(constraints, new ConstraintOrdering(n, constraints), new ArrayCPA(), 0);
		//				m.solve(constraints, new RandomStaticOrdering(n), new ArrayCPA(), 0);
		m.solve(constraints, new DefaultOrdering(n), new ArrayCPA(), 0, new EqualPriority());
	}
}
